/* Papastefanos Serafeim */
/* Sunarthseis gia asfalh epikoinwnia */

#include "low.h"
#include "helpers.h"
#include "secure.h"


void handle_error(const char *file, int lineno, const char *msg) {
    fprintf(stderr, "** %s:%i %s\n", file, lineno, msg);
    ERR_print_errors_fp(stderr);
}

void init_OpenSSL(void) {
    if (!SSL_library_init()) {
        fprintf(stderr, "** OpenSSL initialization failed!\n");
        exit(-1);
    }
    SSL_load_error_strings();
}

/* Auth h sunarthsh uparxei sto vivlio Network Security with Openssl,
ths O'Reilly */
int verify_callback(int ok, X509_STORE_CTX *store) {
    char data[256];
 
    if (!ok) {
        X509 *cert = X509_STORE_CTX_get_current_cert(store);
        int  depth = X509_STORE_CTX_get_error_depth(store);
        int  err = X509_STORE_CTX_get_error(store);
 
        fprintf(stderr, "-Error with certificate at depth: %i\n", depth);
        X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
        fprintf(stderr, "  issuer   = %s\n", data);
        X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
        fprintf(stderr, "  subject  = %s\n", data);
        fprintf(stderr, "  err %i:%s\n", err, X509_verify_cert_error_string(err));
    }
 
    return ok;
}

/* Auth h sunarthsh uparxei sto vivlio Network Security with Openssl,
ths O'Reilly */
long post_connection_check(SSL *ssl, char *host) {
    X509      *cert;
    X509_NAME *subj;
    char      data[256];
    int       extcount;
    int       ok = 0;
 
    /* Checking the return from SSL_get_peer_certificate here is not strictly
     * necessary.  With our example programs, it is not possible for it to return
     * NULL.  However, it is good form to check the return since it can return NULL
     * if the examples are modified to enable anonymous ciphers or for the server
     * to not require a client certificate.
     */
    if (!(cert = SSL_get_peer_certificate(ssl)) || !host) goto err_occured;
    if ((extcount = X509_get_ext_count(cert)) > 0) {
        int i;
 
        for (i = 0;  i < extcount;  i++) {
            char              *extstr;
            X509_EXTENSION    *ext;
 
            ext = X509_get_ext(cert, i);
            extstr = (char*) OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
 
            if (!strcmp(extstr, "subjectAltName")) {
                int                  j;
                unsigned char        *data;
                STACK_OF(CONF_VALUE) *val;
                CONF_VALUE           *nval;
                X509V3_EXT_METHOD    *meth;
                void                 *ext_str = NULL;
 
                if (!(meth = X509V3_EXT_get(ext))) break;
                data = ext->value->data;

#if (OPENSSL_VERSION_NUMBER > 0x00907000L)
                if (meth->it) ext_str = ASN1_item_d2i(NULL, &data, ext->value->length, ASN1_ITEM_ptr(meth->it));
                else ext_str = meth->d2i(NULL, &data, ext->value->length);
#else
                ext_str = meth->d2i(NULL, &data, ext->value->length);
#endif
                val = meth->i2v(meth, ext_str, NULL);
                for (j = 0;  j < sk_CONF_VALUE_num(val);  j++) {
                    nval = sk_CONF_VALUE_value(val, j);
                    if (!strcmp(nval->name, "DNS") && !strcmp(nval->value, host)) {
                        ok = 1;
                        break;
                    }
                }
            }
            if (ok) break;
        }
    }
 
    if (!ok && (subj = X509_get_subject_name(cert)) && X509_NAME_get_text_by_NID(subj, NID_commonName, data, 256) > 0) {
        data[255] = 0;
        if (strcasecmp(data, host) != 0) goto err_occured;
    }
 
    X509_free(cert);
    return SSL_get_verify_result(ssl);
 
err_occured:
    if (cert) X509_free(cert);
    return X509_V_ERR_APPLICATION_VERIFICATION;
}

void seed_prng(void) {
	#ifdef _CWMP_WIN
	RAND_screen();
	#else
	RAND_load_file("/dev/urandom", 1024);
	#endif

}

/* Me th sunarthsh auth o client sundeetai sth d/sh address kai th 9ura port */
SSL* makeConnectionSSL(char *address, int port, SSL_CTX *ctx) {
	BIO *conn;
	SSL *ssl;
	long    err;

	conn = BIO_new(BIO_s_connect() );
	BIO_set_conn_hostname(conn, address);
	BIO_set_conn_int_port(conn, &port);
	if (!conn) {
		int_error("Error creating connection BIO");
		return NULL;
	}
	if (BIO_do_connect(conn) <= 0) {
		int_error("Error connecting to remote machine");
		return NULL;
	}

	ssl = SSL_new(ctx);
	SSL_set_bio(ssl, conn, conn);
	if (SSL_connect(ssl) <= 0) {
		int_error("Error connecting SSL object");
		return NULL;
	}
	if ((err = post_connection_check(ssl, SERVER_COMMON_NAME)) != X509_V_OK) {
		fprintf(stderr, "-Error: peer certificate: %s\n", X509_verify_cert_error_string(err));
		int_error("Error checking SSL object after connection");
		return NULL;
	}

	return ssl;
}


/* Epistrefei thn epomenh grammh apo to socket. Xreiazetai mono kata
 * thn anagnwsh ths epikefalidas tou HTTP mhnumatos. Epistrefetai enas
 * NULL pointer se periptwsh sfalmatos. H sunarthsh auth ousiastika 
 * diavazei sunexws enan enan tous xarakthres apo to socket mexri na
 * diavasei ton CR (\r). Sth sunexeia, ejetazei an o epomenos xarakthras 
 * einai o LF (\n) opote jeroume oti h grammh exei teleiwsei, alliws
 * sunexizetai h anagnwsh xarakthrwn. */
char *getLineSSL(SSL *ssl) {
	char c;
	char buf[MAXBUF]; 
	int i=0;
	while(1) {
		if( SSL_read(ssl, &c, 1) <=0 ) {
			perror("SSL_read, CANNOT READ 1 char");
			return NULL;
		};

		buf[i++]=c;
		if(i==MAXBUF-1) {
			printf("HTTP header line too long");
			return NULL;
		}

		if(c=='\r') { // GOT CR
			SSL_read(ssl, &c, 1);
			if(c == '\n') { // GOT LF
				buf[i++]=c;
				break ; 
			} 
		}
	}
	// Gia na ftasoume edw exei lhf9ei oloklhrh grammh
	char *tmp = (char *)malloc((i+1)*sizeof(char) );
	strncpy(tmp, buf, i);
	tmp[i]='\0';
	return tmp;
}

/* H sunarthsh auth diavazei ena HTTP mhnuma apo to socket. Arxika,
 * diavazei grammh-grammh thn epikefalida mexri na vrei th grammh
 * Content-Length: h opoia kai dinei to mege9os tou HTTP mhnumatos
 * to opoio kai apo9hkeuei. Sth sunexeia, sunexizei na diavazei grammes
 * mexri na vrei mia kenh grammh. Apo ekei kai pera, sumfwna me to HTTP
 * prwtokollo, 9a uparxoun tosoi xarakthres osoi kai to mege9s tou
 * mhnumatos pou diavasthke pio prin, opote kai diavazontai oloi, me
 * th xrhsh epimonou tropou wste na einai sigouro oti de 9a lhf9ei miso
 * mhnuma. */
char *getRawMessageSSL(SSL *ssl) {
	int len;
	char *tmp;
	while(1) {
		tmp=getLineSSL(ssl);
		//puts(tmp);
		if(tmp == NULL) return NULL;
		if(strstr(tmp, "Content-Length:") ) {
			
			len=atoi(&tmp[15]);
			//if(len==0) return NULL; 
		}

		if(tmp[0]=='\r') { //Adeia grammh
			free(tmp);	
			break;
		}
		free(tmp);
	}

	if(len==0) return NULL; 
	char *raw=(char *)malloc((len+1)*sizeof(char ) ) ;
	int totalbytes=0;
	int bytes=0;
	while(totalbytes<len) {
		bytes=SSL_read(ssl, raw+totalbytes, len-totalbytes);
		totalbytes+=bytes;
	}
	raw[len]=0;
	
	return raw;
}


/* Epistrefei ta SOAP mhnumata pou uparxoun sto socket xrhsimopoiwntas
 * tis getRawMessage() kai splitRawMessage */
StringList *getMessagesSSL(SSL *ssl) {
	StringList *slist;
	char *buf = getRawMessageSSL(ssl);
	if(buf == NULL) {
		slist = newStringList(0);
		return slist;
	}
	slist = splitRawMessage(buf);
	free(buf); 
	return slist;
}


/* Stellei ta mhnumata messages ws HTTP mhnuma. Afou parei ta headers apo thn
 * createHeaders, antigrafei headers kai mhnumata se ena string katallhlou 
 * mege9ous, to opoio kai stelletai sth sunexeia mesw tou socket. */
void sendRawMessageSSL(SSL *ssl, StringList *messages) {
	unsigned int msglen=0, totalen=0, pos=0, i;
	for(i=0;i<messages->size;i++) msglen+=strlen(messages->strings[i]);
	StringList *headers = createHeaders(msglen);
	
	for(i=0;i<headers->size;i++) totalen+=strlen(headers->strings[i]);
	totalen+=msglen+1;

	char *raw = (char *)malloc(totalen*sizeof(char) ) ;
	
	for(i=0;i<headers->size;i++) {
		strncpy(raw+pos, headers->strings[i], strlen(headers->strings[i]) );
		pos+=strlen(headers->strings[i]);
	}

	for(i=0;i<messages->size;i++) {
		strncpy(raw+pos, messages->strings[i], strlen(messages->strings[i]) );
		pos+=strlen(messages->strings[i]);
	}

	raw[totalen-1]=0;
	
	SSL_write(ssl, raw, strlen(raw));

	freeStringList(headers);
	free(raw);
}

